package cn.ycl.system.other.study.leetCode;


import java.util.*;
import java.util.concurrent.FutureTask;

public class HuffmanCode {
    public static void main(String[] args) {
        // 需要被编码的字符串
        String content = "i like like like java do you like a java";
        // 得到字符串的字节数组
        byte[] contentBytes = content.getBytes();
        // 尝试输出该字节数组，发现是40个十进制数字
        System.out.println(Arrays.toString(contentBytes));
//		[105, 32, 108, 105, 107, 101, 32, 108, 105, 107, 101, 32, 108,
//				105, 107, 101, 32, 106, 97, 118, 97, 32, 100, 111, 32,
//				121, 111, 117, 32, 108, 105, 107, 101, 32, 97, 32, 106,
//				97, 118, 97]
        //测试是否转换成了Node对象
        List<Node> nodes = getNodes(contentBytes);
        System.out.println(nodes);

        //测试一把创建的哈夫曼树
        System.out.println("以下是哈夫曼树：");
        Node root = createHuffmanTree(nodes);
        System.out.println("前序遍历");
        root.preOrder();

        //测试一把是否生成了对应的哈夫曼编码
		//根结点是没有路径的，故设置为空
        getCodes(root, "", stringBuilder);
        System.out.println("以下是Map路径：");
        //遍历map主要是为了取得key和value的值来使用，若只是单纯输出，直接输出huffmanCodes即可,这里只是为了练习map遍历，写复杂了
        for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }

        //测试一下路径拼接后的唯一一个长路径
        byte[] zip = zip(contentBytes, huffmanCodes);
		//得到17个十进制数字，比之前的40个十进制数字，压缩率为（40-17）/40=57.5%
        System.out.println(Arrays.toString(zip));
    }


    /**
     * 此方法的作用就是将得到的Map中的多个路径拼接成一个字符串,然后将字符串按每8位进行分割，再转成十进制
     * 其中bytes就是程序开始得到的字符数组contentBytes，huffMap就是路径的Map：huffmanCodes
     */

    private static byte[] zip(byte[] bytes, Map<Byte, String> huffMap) {
        int index = 0;
        String temp;
        StringBuilder stringBuilder = new StringBuilder();
        //遍历byte数组，将map中的路径拼接
        for (byte b : bytes) {
            stringBuilder.append(huffMap.get(b));
        }
        //判断该字符串可以组成多少个十进制数
        int len = 0;
        if (stringBuilder.length() % 8 == 0) {
            len = stringBuilder.length() / 8;
        } else {
            len = stringBuilder.length() / 8 + 1;
        }
        //将字符串截取，放入字符数组
        byte[] countss = new byte[len];
        for (int i = 0; i < stringBuilder.length(); i = i + 8) {

            if (i + 8 > stringBuilder.length()) {
                temp = stringBuilder.substring(i);
                countss[index] = (byte) Integer.parseInt(temp, 2);
                break;
            }
            temp = stringBuilder.substring(i, i + 8);
            //将截取的字符串转换成数字Integer.parseInt(temp,2)表示，temp的基数是二进制
            countss[index] = (byte) Integer.parseInt(temp, 2);
            index++;
        }
        return countss;
    }


    /**
     * 此方法的作用就是生成赫夫曼树对应的赫夫曼编码
     * 思路：
     * 1.将赫夫曼编码表存放在Map<Byte,String>
     * 2.在生成赫夫曼编码表时，需要去拼接路径，定义一个StringBuilder，存储某个叶子结点的路径
     */

    static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
    static StringBuilder stringBuilder = new StringBuilder();

    //其中code就是路径：规定：左子结点是0，右子结点；  stringBuilder是用于拼接路径
    private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
        StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
        //将code加入到stringBuilder2中
        stringBuilder2.append(code);
        if (node != null) {
            if (node.data == null) {//data空表示是非叶子结点
                //向左递归
                getCodes(node.left, "0", stringBuilder2);
                //向右递归
                getCodes(node.right, "1", stringBuilder2);
            } else {//data不为空表示是叶子结点，一条路径完成，将stringbuilder2中的路径放入map中
                huffmanCodes.put(node.data, stringBuilder2.toString());
            }
        }
    }

    /**
     * 此方法的作用就是将字节数组转换成为List形式的node对象
     */
    private static List<Node> getNodes(byte[] bytes) {
        //1创建一个ArrayList
        ArrayList<Node> nodes = new ArrayList<Node>();

        //2遍历bytes ,统计每一个byte出现的次数-》使用map[key,value]
        Map<Byte, Integer> counts = new HashMap<>();
        for (byte b : bytes) {
            Integer count = counts.get(b);
            if (Objects.isNull(count)) {
                counts.put(b, 1);
            } else {
                counts.put(b, count + 1);
            }
        }
        //把每一个键值对转成一个Node对象，并加入到nodes集合中
        for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
            nodes.add(new Node(entry.getKey(), entry.getValue()));
        }
        return nodes;
    }

    /**
     * 此方法的作用就是通过List，创建对应的赫夫曼树
     */
    private static Node createHuffmanTree(List<Node> nodes) {
        //排序
        while (nodes.size() > 1) {
            Collections.sort(nodes);
            //得到前两个对象和创建父结点
            Node leftNode = nodes.get(0);
            Node rightNode = nodes.get(1);
            Node parentNode = new Node(null, leftNode.weight + rightNode.weight);
            //关联父子结点和移除子结点，加入父结点
            parentNode.left = leftNode;
            parentNode.right = rightNode;
            nodes.remove(leftNode);
            nodes.remove(rightNode);
            nodes.add(parentNode);
        }
        //返回最后的一个结点
        return nodes.get(0);
    }


}